/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_srv_property.c
 *
 * Purpose: implementation property for cbus service
 *
 * Developer:
 *   wen.gu , 2022-02-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "cbus/bus/cbus_service_property.h"
#include <string.h>

#include "cbus/bus/cbus_message.h"
#include "cbus/core/cbus_map.h"
#include "cbus/bus/cbus_service.h"
#include "cbus_service_private.h"
#include "cbus/bus/cbus_event_publisher.h"
#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define PROPERTY_REG(service_handle, msg_type, on_func, opaque_handle, func_name) do{                      \
    GErrc ret = cbus_service_message_register_category(service_handle, msg_type, on_func, opaque_handle);  \
    if (G_OK != ret){                                                                                      \
        LOGE("register property "  func_name  " method failed (err:0x%08x)\n", ret);                         \
        return ret;                                                                                        \
    }                                                                                                      \
}while(0)

#define PROPERTY_UNREG(service_handle, msg_type, func_name) do{                      \
    GErrc ret = cbus_service_message_unregister_category(service_handle, msg_type);  \
    if (G_OK != ret){                                                                \
        LOGE("unregister property " func_name " method failed (err:0x%08x)\n", ret); \
        /** return ret; */                                                           \
    }                                                                                \
}while(0)

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

typedef struct _cbus_srv_property_info
{
    GU16 notify_session_id; /** used to calculate session id for property notification */
    GU32 property_id;    
    GBOL has_notification;
    GPTR opaque;
    cbus_service_property_method on_set;
    cbus_service_property_method on_get;   
    cbus_service_property_compare_value_func on_compare; /** is NULL, will use default compare: memcmp */
    cbus_message_t value;  /** default value,  the property must be have a default value */     

    cbus_event_publisher_t event_publisher;
}cbus_srv_property_info_t;

struct _cbus_srv_property
{
    GBOL is_started;
    /** a map for property id and info, like map<property_id, property_info*> */
    cbus_map_t property_map;

    cbus_service_t service_ref; /** a reference to servie instance */
};
/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static void cbus_srv_property_info_free(void* ptr)
{
    if (ptr)
    {
        cbus_srv_property_info_t* info = (cbus_srv_property_info_t*)ptr;
        cbus_event_publisher_destroy(info->event_publisher);
        cbus_buffer_free(info->value);
        free(info);
    }
}

static cbus_srv_property_info_t* cbus_srv_property_info_find(cbus_srv_property_t property, GU32 property_id)
{
    cbus_srv_property_info_t* info = NULL;
    cbus_map_find_pointer(property->property_map, property_id, (const void**)&info);
    return info;
}

static cbus_srv_property_info_t* cbus_srv_property_info_add(cbus_srv_property_t property, GU32 property_id)
{
    cbus_srv_property_info_t* info = (cbus_srv_property_info_t*)malloc(sizeof(cbus_srv_property_info_t));

    if (info)
    {
        info->event_publisher = cbus_event_publisher_create();

        if (!info->event_publisher)
        {
            free(info);
            LOGE("create event publisher instance for property id: %d failed\n", property_id);
            return NULL;
        }

        if (cbus_map_set_pointer(property->property_map, property_id, (const void*)info, cbus_srv_property_info_free) == GFALSE)
        {
            free(info);
            return NULL;
        }
    }

    return info;
}

static GErrc cbus_srv_property_info_remove(cbus_srv_property_t property, GU32 property_id)
{
    return (cbus_map_remove(property->property_map, property_id) == GTRUE) ? G_OK : G_ErrUndefined;
}

static GErrc cbus_srv_property_info_clear(cbus_srv_property_t property)
{
    cbus_map_clear(property->property_map);
    return G_OK;
}

static void cbus_srv_property_on_set(cbus_message_t message)
{
    cbus_srv_property_t property = (cbus_srv_property_t)cbus_message_get_opaque(message);

    cbus_srv_property_info_t* info = cbus_srv_property_info_find(property, cbus_message_get_message_id(message));

    if (info)
    {
        if (info->on_set)
        {
            cbus_message_set_opaque(message, info->opaque);
            info->on_set(message);
        }
        else
        {
            cbus_srv_property_update_value(property, message);
        }
        
        cbus_message_set_message_type(info->value, CBUS_MSG_TYPE_Property_Set_Ack);
        cbus_endpoint_send(cbus_message_get_endpoint_ref(message), info->value);
    }
    else
    {
        cbus_message_send_response_code(message, CBUS_MSG_ErrInsufficientResources);
    }
}

static void cbus_srv_property_on_get(cbus_message_t message)
{
    cbus_srv_property_t property = (cbus_srv_property_t)cbus_message_get_opaque(message);

    cbus_srv_property_info_t* info = cbus_srv_property_info_find(property, cbus_message_get_message_id(message));

    if (info)
    {
        if (info->on_get)
        {
            cbus_message_set_opaque(message, info->opaque);
            info->on_get(message);
        }
        
        cbus_message_set_message_type(info->value, CBUS_MSG_TYPE_Property_Get_Ack);
        cbus_endpoint_send(cbus_message_get_endpoint_ref(message), info->value);
    }
    else
    {
        cbus_message_send_response_code(message, CBUS_MSG_ErrNotFound);
    }
}

static GBOL cbus_srv_property_default_compare(const cbus_message_t old_value, const cbus_message_t new_value)
{
    return memcmp(cbus_message_get_payload(old_value), cbus_message_get_payload(new_value), cbus_message_get_payload_size(new_value)) != 0; 
}

static cbus_srv_property_info_t* cbus_srv_property_clone_info(const cbus_srv_property_info_t* info)
{
    cbus_srv_property_info_t* new_info = (cbus_srv_property_info_t*)malloc(sizeof(cbus_srv_property_info_t));

    if (new_info)
    {
        memcpy(new_info, info, sizeof(cbus_srv_property_info_t));
    }

    return new_info;
}

static void event_on_remove(GPTR opaque, GU64 key, GU32 type, const void* value, GU32 value_size)
{
    cbus_srv_property_info_t* info = (cbus_srv_property_info_t*)value;

    //struct _endpoint_info* ep_info = (struct _endpoint_info*)opaque;

    cbus_event_publisher_clear_subsciber(info->event_publisher);
}

static void property_clear_subscriber(cbus_srv_property_t property)
{
    cbus_map_for_each(property->property_map, event_on_remove, NULL);
}

static void property_on_subscribe(cbus_message_t message)
{
    cbus_srv_property_info_t* info = cbus_srv_property_info_find(cbus_message_get_opaque(message), cbus_message_get_message_id(message));

    if (info)
    {
        cbus_message_set_opaque(message, info->event_publisher);
        cbus_event_publisher_on_subscribe(message);
    }
    else
    {
        LOGE("not found the property with id : %d, for subscribe\n", cbus_message_get_message_id(message));
        cbus_message_send_response_code(message, CBUS_MSG_ErrNotFound);
    }
}

static void property_on_stop_subscribe(cbus_message_t message)
{
    cbus_srv_property_info_t* info = cbus_srv_property_info_find(cbus_message_get_opaque(message), cbus_message_get_message_id(message));

    if (info)
    {
        cbus_message_set_opaque(message, info->event_publisher);
        cbus_event_publisher_on_stop_subscribe(message);
    }
    else
    {
        LOGE("not found the property with id: %d, for stop subscribe\n", cbus_message_get_message_id(message));
        cbus_message_send_response_code(message, CBUS_MSG_ErrNotFound);
    }
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

cbus_srv_property_t cbus_srv_property_create(cbus_service_t service_ref)
{
    cbus_srv_property_t property = (cbus_srv_property_t)malloc(sizeof(struct _cbus_srv_property));

    if (property)
    {
        memset(property, 0, sizeof(struct _cbus_srv_property));
        property->property_map = cbus_map_create(CMV_TYPE_Pointer, CBUS_MAP_DEFAULT_CAPACITY);
        if (!property->property_map)
        {
            cbus_srv_property_destroy(property);
            return NULL;
        }

        property->service_ref = service_ref;  
        property->is_started = GFALSE;   
    }
    else
    {
        LOGE("alloc property instance failed for\n" );
    }

    return property;
}

void cbus_srv_property_destroy(cbus_srv_property_t property)
{
    if (property)
    {
        cbus_srv_property_stop(property);
        cbus_map_destroy(property->property_map);
        free(property);
    }
}

/** offer current property(online) */
GErrc cbus_srv_property_offer(cbus_srv_property_t property)
{
    if (!property)
    {
        return G_ErrBadParameter;
    }

    if (!property->service_ref || (property->is_started == GTRUE))
    {
        return G_ErrInvalidStatus;
    }

    PROPERTY_REG(property->service_ref, CBUS_MSG_TYPE_Property_Set, cbus_srv_property_on_set, property, "set");
    PROPERTY_REG(property->service_ref, CBUS_MSG_TYPE_Property_Get, cbus_srv_property_on_get, property, "get");
    PROPERTY_REG(property->service_ref, CBUS_MSG_TYPE_Property_Subscribe, property_on_subscribe, property, "subcribe");
    PROPERTY_REG(property->service_ref, CBUS_MSG_TYPE_Property_Stop_Subscribe, property_on_stop_subscribe, property, "stop subscribe");


    property->is_started = GTRUE;
    return G_OK;
}

/** stop current property(offline) */
GErrc cbus_srv_property_stop(cbus_srv_property_t property)
{
    if (!property)
    {
        return G_ErrBadParameter;
    }

    if (!property->service_ref || (property->is_started == GFALSE))
    {
        return G_ErrInvalidStatus;
    }

    PROPERTY_UNREG(property->service_ref, CBUS_MSG_TYPE_Property_Set, "set");
    PROPERTY_UNREG(property->service_ref, CBUS_MSG_TYPE_Property_Get, "get");
    PROPERTY_UNREG(property->service_ref, CBUS_MSG_TYPE_Property_Subscribe, "subscribe");
    PROPERTY_UNREG(property->service_ref, CBUS_MSG_TYPE_Property_Stop_Subscribe, "stop subscribe");

    property_clear_subscriber(property);

    property->is_started = GFALSE;
    property->service_ref = NULL;

    return G_OK;
}

GErrc cbus_srv_property_register(cbus_srv_property_t property, const cbus_service_property_item_t* property_info)
{
    if (!property || !property_info)
    {
        return G_ErrBadParameter;
    }

    if (!property->property_map)
    {
        return G_ErrInvalidStatus;
    }

    cbus_srv_property_info_t* info = cbus_srv_property_info_find(property, property_info->property_id);

    if (!info) 
    {
        info = cbus_srv_property_info_add(property, property_info->property_id);
        if (!info)
        {
            return G_ErrInsufficientResources;
        }

    }/** todo, is need return a error: has exist */

    info->property_id      = property_info->property_id;
    info->has_notification = property_info->has_notification;
    info->opaque           = property_info->opaque;
    info->on_set           = property_info->on_set;
    info->on_get           = property_info->on_get;
    info->value            = cbus_message_reference(property_info->default_value);

    return G_OK;
}

GErrc cbus_srv_property_register_group(cbus_srv_property_t property, const cbus_service_property_item_t* group, GU32 group_size)
{
    if (!property || !group || (group_size == 0))
    {
        return G_ErrBadParameter;
    }

    if (!property->property_map)
    {
        return G_ErrInvalidStatus;
    }

    for (GU32 i = 0; i < group_size; i++)
    {
        GErrc ret = cbus_srv_property_register(property, &group[i]);

        if (G_OK != ret) /** todo, process the error code */
        {
            LOGE("register property(%d) failed(err:0x%08x)\n", group[i].property_id, ret);
        }
    }
    
    return G_OK;
}

GErrc cbus_srv_property_unregister(cbus_srv_property_t property, GU32 property_id)
{
    if (!property)
    {
        return G_ErrBadParameter;
    }

    if (!property->property_map)
    {
        return G_ErrInvalidStatus;
    }  

    return cbus_srv_property_info_remove(property, property_id);
}

GErrc cbus_srv_property_unregister_all(cbus_srv_property_t property)
{
    if (!property)
    {
        return G_ErrBadParameter;
    }

    if (!property->property_map)
    {
        return G_ErrInvalidStatus;
    }  

    return cbus_srv_property_info_clear(property);
}


GErrc cbus_srv_property_update_value(cbus_srv_property_t property, cbus_message_t value)
{
    if (!property || !value) 
    {
        return G_ErrBadParameter;
    }


    cbus_srv_property_info_t* info = cbus_srv_property_info_find(property, cbus_message_get_message_id(value));

    if (!info)
    {
        return G_ErrNotFound;
    }

    GU32 old_size = cbus_message_get_payload_size(info->value);
    GU32 new_size = cbus_message_get_payload_size(value);

    if (old_size != new_size) /** todo, refine me, the value buffer size can be change(or different? */
    {
        LOGE("the size of property value changed(old: %d, new: %d), this is illegal\n", old_size, new_size);
        return G_ErrOutOfRange;
    }

    if (info->on_compare(info->value, value) == GTRUE)
    { /** if value changed */

        cbus_message_free(info->value);
        info->value = cbus_message_reference(value);

        cbus_event_publisher_notify(info->event_publisher, info->value);
    }

    return G_OK;
}